package com.ruoyi.framework.web.service;

import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.*;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
import com.ruoyi.system.service.IBigDataServer;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 登录校验方法
 * 
 * @author ruoyi
 */
@Component
public class SysLoginService
{
    // 是否允许账户多终端同时登录（true允许 false不允许）
    @Value("${token.soloLogin}")
    private boolean soloLogin;

    @Autowired
    private TokenService tokenService;

    @Resource
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;
    
    @Autowired
    private ISysUserService userService;

    @Autowired
    private ISysConfigService configService;

    @Autowired
    private IBigDataServer bigDataServer;

    /**
     * 登录验证
     * 
     * @param username 用户名
     * @param password 密码
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    public String login(String username, String password, String code, String uuid)
    {
        // 验证码校验
        validateCaptcha(username, code, uuid);
        // 登录前置校验
        loginPreCheck(username, password);
        // 用户验证
        Authentication authentication = null;
        try
        {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            }
            else
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        }
        finally
        {
            AuthenticationContextHolder.clearContext();
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        recordLoginInfo(loginUser.getUserId());

        if (!soloLogin)
        {
            // 如果用户不允许多终端同时登录，清除缓存信息
            String userIdKey = Constants.LOGIN_USERID_KEY + loginUser.getUser().getUserId();
            String userKey = redisCache.getCacheObject(userIdKey);
            if (StringUtils.isNotEmpty(userKey))
            {
                redisCache.deleteObject(userIdKey);
                redisCache.deleteObject(userKey);
            }
        }
//        this.bigDataInit(loginUser);
        // 生成token
        return tokenService.createToken(loginUser);
    }

//    public void bigDataInit(LoginUser loginUser){
//        BigData bigData = bigDataServer.bigDataInit();
//        if (bigData != null) {
//            String redisKey = loginUser.getUser().getUserId() + WebSocketUsers.BIG_DATA_GROUP;
//            redisCache.setCacheObject(redisKey, bigData);
//            WebSocketUsers.sendMessageToUsersByText(JSON.toJSONString(bigData),WebSocketUsers.BIG_DATA_GROUP);
//        }
//    }


    /**
     * WebSocket登录方法
     * 通过userId和WebSocket连接ID进行登录，成功后通过WebSocket发送登录信息给前端
     *
     * @param userId 用户ID
     * @param wsId WebSocket连接ID
     * @return 登录结果
     */
    public Map<String, Object> wsLogin(String userId, String wsId) {
        Map<String, Object> result = new HashMap<>();

        try {
            // 根据用户ID查询用户信息
            SysUser user = userService.selectUserById(Long.valueOf(userId));
            if (user == null) {
                result.put("success", false);
                result.put("message", "用户不存在");
                return result;
            }

            // 检查用户状态
            if (!"0".equals(user.getStatus())) {
                result.put("success", false);
                result.put("message", "用户已被禁用");
                return result;
            }

            // 使用与账号密码登录相同的认证流程
            Authentication authentication = null;
            try {
                // 创建认证令牌（使用用户名进行认证，密码为空字符串）
                UsernamePasswordAuthenticationToken authenticationToken =
                        new UsernamePasswordAuthenticationToken(user.getUserName(), "");
                AuthenticationContextHolder.setContext(authenticationToken);

                // 由于WebSocket登录是已经通过微信验证的，我们需要特殊处理
                // 完全模仿UserDetailsServiceImpl.createLoginUser的方式创建LoginUser
                LoginUser loginUser = new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));

                // 创建已认证的Authentication对象（使用带有权限列表的构造函数）
                authentication = new UsernamePasswordAuthenticationToken(
                        loginUser,
                        null,
                        loginUser.getAuthorities()
                );
                // 注意：使用带有authorities的构造函数会自动设置为已认证状态，不需要手动调用setAuthenticated(true)

                // 生成token（与账号密码登录完全相同的方式）
                String token = tokenService.createToken(loginUser);

                // 记录登录信息
                recordLoginInfo(user.getUserId());

                // 记录登录日志
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUserName(),
                        Constants.LOGIN_SUCCESS, "WebSocket扫码登录成功"));

                // 准备返回数据
                Map<String, Object> loginData = new HashMap<>();
                loginData.put("token", token);
                loginData.put("user", user);
                loginData.put("loginTime", DateUtils.getNowDate());

                // 准备WebSocket消息
                Map<String, Object> wsMessage = new HashMap<>();
                wsMessage.put("type", "login_success");
                wsMessage.put("message", "登录成功");
                wsMessage.put("token", token);
                wsMessage.put("user", user);
                wsMessage.put("timestamp", System.currentTimeMillis());

                // 直接通过WebSocket发送登录成功消息
                boolean sendSuccess = com.ruoyi.system.websocket.WxLoginWebSocketServer.sendMessageToClient(
                    wsId, com.alibaba.fastjson.JSON.toJSONString(wsMessage)
                );

                result.put("success", true);
                result.put("message", "登录成功");
                result.put("data", loginData);
                result.put("token", token);
                result.put("wsSendSuccess", sendSuccess);

            } catch (Exception e) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUserName(),
                        Constants.LOGIN_FAIL, "WebSocket登录失败：" + e.getMessage()));

                result.put("success", false);
                result.put("message", "认证失败：" + e.getMessage());
            } finally {
                AuthenticationContextHolder.clearContext();
            }

        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "登录过程中发生异常：" + e.getMessage());
        }

        return result;
    }

    /**
     * 不需要验证码登录
     *
     * **/
    @Autowired
    private SysPermissionService permissionService;

    /**
     * 通过临时秘钥登录
     * 
     * @param tempSecret 临时秘钥
     * @return token
     */
    public String loginByTempSecret(String tempSecret)
    {
        try {
            // 根据临时秘钥查询用户
            SysUser user = userService.selectUserByTempSecret(tempSecret);
            if (user == null) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor("未知用户", Constants.LOGIN_FAIL, "临时秘钥无效"));
                throw new ServiceException("临时秘钥无效或用户不存在");
            }

            // 检查用户状态
            if (!"0".equals(user.getStatus())) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUserName(), Constants.LOGIN_FAIL, "用户已被禁用"));
                throw new ServiceException("用户已被禁用");
            }

            // 验证Redis中的临时秘钥是否还有效
            String redisKey = "temp_secret:" + user.getUserId();
            Map<String, Object> secretInfo = redisCache.getCacheObject(redisKey);
            if (secretInfo == null) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUserName(), Constants.LOGIN_FAIL, "临时秘钥已过期"));
                throw new ServiceException("临时秘钥已过期");
            }
            
            String redisTempSecret = (String) secretInfo.get("tempSecret");
            if (!tempSecret.equals(redisTempSecret)) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUserName(), Constants.LOGIN_FAIL, "临时秘钥不匹配"));
                throw new ServiceException("临时秘钥不匹配");
            }

            // 创建LoginUser对象
            Set<String> permissions = permissionService.getMenuPermission(user);
            LoginUser loginUser = new LoginUser(user.getUserId(), user.getDeptId(), user, permissions);
            
            // 创建认证对象
            Authentication authentication = new UsernamePasswordAuthenticationToken(
                loginUser,
                null,
                loginUser.getAuthorities()
            );
            
            // 记录登录信息
            recordLoginInfo(user.getUserId());
            
            // 记录登录日志
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(user.getUserName(), 
                Constants.LOGIN_SUCCESS, "临时秘钥登录成功"));

            // 清理Redis中的临时秘钥（一次性使用）
            redisCache.deleteObject(redisKey);
            
            // 清理数据库中的临时秘钥 - 使用直接SQL更新，避免影响角色关联
            // 不使用updateUser方法，因为它会删除用户的角色关联
//            SysUser updateUser = new SysUser();
//            updateUser.setUserId(user.getUserId());
//            updateUser.setTempSecret("");
//            // 使用updateUserProfile方法，它不会影响角色关联
//            userService.updateUserProfile(updateUser);

            // 生成token
            return tokenService.createToken(loginUser);
            
        } catch (ServiceException e) {
            throw e;
        } catch (Exception e) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor("未知用户", Constants.LOGIN_FAIL, 
                "临时秘钥登录异常：" + e.getMessage()));
            throw new ServiceException("登录失败：" + e.getMessage());
        }
    }
    public String login2(SysUser sysUser)
    {

        Authentication authentication = null;
        try
        {
            Set<String> permissions = permissionService.getMenuPermission(sysUser);
            UserDetails userDetails = new LoginUser(sysUser.getUserId(), sysUser.getDeptId(), sysUser, permissions);
            authentication = new UsernamePasswordAuthenticationToken(userDetails, null,AuthorityUtils.createAuthorityList(permissions.toArray(new String[0])));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            }
            else
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        }
        finally
        {
            AuthenticationContextHolder.clearContext();
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        return tokenService.createToken(loginUser);
    }
    /**
     * 校验验证码
     * 
     * @param username 用户名
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    public void validateCaptcha(String username, String code, String uuid)
    {
        boolean captchaEnabled = configService.selectCaptchaEnabled();
        if (captchaEnabled)
        {
            String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
            String captcha = redisCache.getCacheObject(verifyKey);
            redisCache.deleteObject(verifyKey);
            if (captcha == null)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
                throw new CaptchaExpireException();
            }
            if (!code.equalsIgnoreCase(captcha))
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
                throw new CaptchaException();
            }
        }
    }

    /**
     * 登录前置校验
     * @param username 用户名
     * @param password 用户密码
     */
    public void loginPreCheck(String username, String password)
    {
        // 用户名或密码为空 错误
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
            throw new UserNotExistsException();
        }
        // 密码如果不在指定范围内 错误
        if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
                || password.length() > UserConstants.PASSWORD_MAX_LENGTH)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // 用户名不在指定范围内 错误
        if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                || username.length() > UserConstants.USERNAME_MAX_LENGTH)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // IP黑名单校验
        String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
            throw new BlackListException();
        }
    }

    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId)
    {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(IpUtils.getIpAddr());
        sysUser.setLoginDate(DateUtils.getNowDate());
        userService.updateUserProfile(sysUser);
    }
}
